<?php
	
	/**
	 * Verifies bootstrapper execution to ensure file
	 * necessities, and disabling direct access
	 */
	defined('ENGINE') or exit;
	
	/**
	 * Base database driver interface
	 *
	 * This defines the driver structure of which functions a driver 
	 * must implement and how they should be called.
	 */
	interface Database_Driver
	{
		/**
		 * Returns if the current system supports the  driver, if this 
		 * method isn't called, a driver may start shutting down or 
		 * throwing random exceptions unexpectedly
		 */
		public function is_driver_supported();

		/**
		 * Connect to a database, if no connection isn't already 
		 * active
		 */
		public function connect(array $configuration = NULL);

		/**
		 * Close a database connection
		 */
		public function close();

		/**
		 * Checks if a connection is active
		 */
		public function is_connected();

		/**
		 * Checks if a variable is a connection of the same type 
		 * as the one used by the driver
		 */
		public function is_link($link);

		/**
		 * Checks if the current connection is persistent
		 */
		public function is_persistent();

		/**
		 * Checks if a variable is a result of the same type as 
		 * the one used by the driver
		 */
		public function is_result($result);

		/**
		 * Get the error message from the last occured error
		 * error
		 */
		public function error();

		/**
		 * Get the error number from the last occured error
		 */
		public function errno();
		
		/**
		 * List all of the tables in the database
		 */
		public function list_tables($use_buffer = true);
		
		/**
		 * Determines if a database table exists
		 */
		public function table_exists($table = false, $use_buffer = true);

		/**
		 * Get the last insert id from last executed SELECT statement
		 */
		public function insert_id();

		/**
		 * Get the number of affected rows from last INSERT INTO/UPDATE/DELETE 
		 * operation.
		 */
		public function affected_rows($result);

		/**
		 * Escape a piece of data using the database specific 
		 * escape method
		 */
		public function escape($data);

		/**
		 * Executes a query and returns the result on SELECT 
		 * statements
		 */
		public function query($sql);
		
		/**
		 * Parses SQL syntax to run multiple queries
		 */
		public function multi_query($sql);
	}
	
	/**
	 * Base database driver result interface
	 *
	 * This defines the driver structure of which functions a driver 
	 * must implement in the result object and how they should be called.
	 */
	interface Database_Driver_Result
	{
		/**
		 * Frees the result from memory, and makes it unusable
		 */
		public function free();

		/**
		 * Checks whenever the result is freed or not
		 */
		public function is_freed();

		/**
		 * Get the number of rows in the result
		 */
		public function num_rows();

		/**
		 * Fetch result with both associative and indexed indexes array
		 */
		public function fetch_array();

		/**
		 * Fetches the result and returns an associative array
		 */
		public function fetch_assoc();

		/**
		 * Fetches the result and returns an indexed array
		 */
		public function fetch_row();

		/**
		 * Fetches the result and returns an object, with overloaded 
		 * properties for rows names
		 */
		public function fetch_object();
	}
	
	/**
	 * Abstract database class
	 *
	 * Every main driver class must extend this class in order to be loadable 
	 * and to comply with the database access layer interface. This also contains 
	 * the factory method used to instanciate a new database driver instance.
	 */
	abstract class Database implements Database_Driver
	{
		/**
		 * Link pointer, this contains the internal link 
		 * to the database from the driver
		 */
		protected $link;

		/**
		 * List of executed queries during execution
		 */
		protected $queries					= array();
		
		/**
		 * Tables buffer used for semi-caching
		 */
		protected $tables					= array();

		/**
		 * Database specific configuration array
		 */
		protected $configuration			= array();

		/**
		 * List of shutdown queries that will be executed 
		 * when then destructor is called
		 */
		protected $shutdown_queries			= array();

		/**
		 * List of loaded drivers used for caching in the 
		 * special required cases where more than one driver 
		 * have to be loaded
		 */
		protected static $loaded_drivers 	= array();


		/**
		 * Default constructor for a new database instance
		 */
		public function __construct(array $configuration)
		{
			if(get_class($this) == __CLASS__)
			{
				throw new Engine_Basic_Exception('Cannot call base constructor directly from a non-initalized instance');
			}

			$this->configuration = $configuration;

			if(!$this->is_driver_supported())
			{
				throw new Engine_Basic_Exception('Unable to load database driver, one or more of the driver dependencies is missing');
			}

			$this->connect();
		}

		/**
		 * Default destructor, this simply closes a database connection 
		 * without anything else. A driver may extend the destructor 
		 * to shutdown other required services.
		 */
		public function __destruct()
		{
			if(!$this->is_connected() || !sizeof($this->shutdown_queries))
			{
				return;
			}

			foreach($this->shutdown_queries as $n => $sql)
			{
				try
				{
					$this->query($sql);
				}
				catch(Engine_Basic_Exception $e)
				{
					if(DEBUG)
					{
						engine_doc_error($e);
					}
				}
			}

			$this->close();
		}

		/**
		 * Magic method called when creating a new instance of the 
		 * object from the registry
		 */
		final public static function invoke(Engine $engine, array $configuration)
		{
			if(!isset($configuration['database']) || !isset($configuration['database']['driver']))
			{
				throw new Engine_Basic_Exception('No database configuration found or no driver defined');
			}

			return(self::factory($configuration['database']['driver'], $configuration['database']));
		}

		/**
		 * Constructs a new database instance
		 */
		final public static function factory($driver, array $configuration)
		{
			$driver 		= (string) $driver;
			$path			= DRIVERS_DIR . DIRECTORY_SEPARATOR . 'driver_' . $driver . '.php';
			$class			= 'Database_Driver_' . $driver;
			$result_class	= $class . '_Result';

			if(in_array($driver, self::$loaded_drivers))
			{
				return(new $class($configuration));
			}
			
			if(!file_exists($path))
			{
				throw new Engine_Basic_Exception('Attempting to use a driver that does not exist, your configuration has become corrupted');
			}
			
			require($path);

			if(!class_exists($class, false) || !class_exists($result_class, false))
			{
				throw new Engine_Basic_Exception('Corrupt database driver, driver classes was not found');
			}

			if(!is_subclass_of($class, __CLASS__) || !is_subclass_of($result_class, 'Database_Result'))
			{
				throw new Engine_Basic_Exception('Corrupt database driver, driver classes does not follow the driver specification');
			}

			self::$loaded_drivers[] = $driver;

			return(new $class($configuration));
		}

		/**
		 * Sets a new query to execute at shutdown
		 */
		public function set_shutdown_query($sql)
		{
			if(func_num_args() > 1)
			{
				$args 		= func_get_args();
				$args[0]	= $sql;
				$sql 		= call_user_func_array('sprintf', $args);
			}

			return($this->shutdown_queries[] = (string) $sql);
		}

		/**
		 * Gets the number of queries executed during this request
		 */
		final public function num_queries()
		{
			return(sizeof($this->queries));
		}
		
		/**
		 * Returns the queries executed up to the point of call
		 */
		final public function get_queries()
		{
			return($this->queries);
		}
	}

	/**
	 * Abstract database result class
	 *
	 * Every driver result class must extend this class in order to be loadable 
	 * and to comply with the database access layer interface.
	 */
	abstract class Database_Result implements Database_Driver_Result
	{
		/**
		 * The database instance from where the result was created
		 */
		protected $instance;

		/**
		 * The result resource
		 */
		protected $result;

		/**
		 * Cached number of rows
		 */
		private $cached_num_rows	= 0;


		/**
		 * Constructs a new result object
		 */
		public function __construct(Database $instance, $result)
		{
			if(!$instance->is_result($result))
			{
				throw new Engine_Basic_Exception('Passed result resource is not a valid result');

			}

			$this->instance			= $instance;
			$this->result 			= $result;
			$this->cached_num_rows	= $this->num_rows();

			if(!$this->cached_num_rows)
			{
				$this->result = NULL;
			}
		}

		/**
		 * Simple destructor to free result when the 
		 * result is unset.
		 */
		public function __destruct()
		{
			$this->free();
		}
	}

	/**
	 * SQL Exception
	 *
	 * Exception designed to carry error information from a failed 
	 * query call.
	 */
	class Engine_SQL_Exception extends Engine_Basic_Exception
	{
		/**
		 * Current SQL state
		 */
		public $sqlstate;

		/**
		 * Constructs a new SQL exception
		 */
		public function __construct($error, $errno, $sqlstate = NULL)
		{
			$this->message 		= $error;
			$this->code			= (integer) $errno;
			$this->sqlstate		= ($sqlstate ? $sqlstate : false);
		}

		/**
		 * Gets the current SQL state if the underlaying database 
		 * driver that threw this exception supports it.
		 */
		public function get_sql_state()
		{
			return($this->sqlstate);
		}
	}

?>